// SPDX-FileCopyrightText: 2019-2023 Giovanni Dante Grazioli <deroad@libero.it>
// SPDX-License-Identifier: BSD-3-Clause

import Extra from './extra.js';
import Base from './base.js';

const _x86_stret = {
	'32': {
		returns: 'rdi',
		receiver: 'eax',
		selector: 'ecx',
		args: ['edx', 'ecx', 'r8d', 'r9d']
	},
	'64': {
		returns: 'rdi',
		receiver: 'rsi',
		selector: 'rdx',
		args: ['rcx', 'r8', 'r9', 'r10']
	}
};

const _x86 = {
	'32': {
		receiver: 'eax',
		selector: 'ecx',
		returns: 'eax',
		args: ['edx', 'ecx', 'r8d', 'r9d']
	},
	'64': {
		receiver: 'rdi',
		selector: 'rsi',
		returns: 'rax',
		args: [
			['edx', 'rdx'],
			['ecx', 'rcx'],
			['r8d', 'r8'],
			['r9d', 'r9']
		]
	}
};

const _ppc = {
	receiver: 'r3',
	selector: 'r4',
	returns: 'r3',
	args: ['r5', 'r6', 'r7', 'r8']
};

const _ppc_stret = {
	returns: 'r3',
	receiver: 'r4',
	selector: 'r5',
	args: ['r6', 'r7', 'r8', 'r9']
};

const _arm = {
	'16': {
		receiver: 'r0',
		selector: 'r1',
		returns: 'r0',
		args: ['r2', 'r3', 'r4', 'r5']
	},
	'32': {
		receiver: 'r0',
		selector: 'r1',
		returns: 'r0',
		args: ['r2', 'r3', 'r4', 'r5']
	},
	'64': {
		receiver: 'x0',
		selector: 'x1',
		returns: 'x0',
		args: ['x2', 'x3', 'x4', 'x5']
	}
};

const _arm_stret = {
	'16': {
		receiver: 'r1',
		selector: 'r2',
		returns: 'r0',
		args: ['r3', 'r4', 'r5', 'r6']
	},
	'32': {
		receiver: 'r1',
		selector: 'r2',
		returns: 'r0',
		args: ['r3', 'r4', 'r5', 'r6']
	}
};

const _object_c_registers = {
	'x86': {
		'32': ['rdi', 'rsi', 'rdx', 'rcx', 'r8', 'r9'],
		'64': ['edi', 'esi', 'edx', 'ecx', 'r8d', 'r9d'],
	},
	'ppc': {
		'32': ['r3', 'r4', 'r5', 'r6', 'r7', 'r8'],
		'64': ['r3', 'r4', 'r5', 'r6', 'r7', 'r8'],
	},
	'arm': {
		'16': ['r0', 'r1', 'r2', 'r3', 'r4', 'r5'],
		'32': ['r0', 'r1', 'r2', 'r3', 'r4', 'r5'],
		'64': ['x0', 'x1', 'x2', 'x3', 'x4', 'x5'],
	}
};

const _object_c_class_methods = {
	objc_msgSend: {
		'x86': _x86,
		'ppc': {
			'32': _ppc,
			'64': _ppc
		},
		'arm': _arm
	},
	objc_msgSend_fpret: {
		'x86': _x86,
		'ppc': {
			'32': _ppc,
			'64': _ppc
		},
		'arm': _arm
	},
	objc_msgSend_noarg: {
		'x86': _x86,
		'ppc': {
			'32': _ppc,
			'64': _ppc
		},
		'arm': _arm
	},
	objc_msgSendSuper: {
		'x86': _x86,
		'ppc': {
			'32': _ppc,
			'64': _ppc
		},
		'arm': _arm
	},
	objc_msgSendSuper2: {
		'x86': _x86,
		'ppc': {
			'32': _ppc,
			'64': _ppc
		},
		'arm': _arm
	},
	objc_msgSend_stret: {
		'x86': _x86_stret,
		'ppc': {
			'32': _ppc_stret,
			'64': _ppc_stret
		},
		'arm': _arm_stret
	},
	objc_msgSendSuper_stret: {
		'x86': _x86_stret,
		'ppc': {
			'32': _ppc_stret,
			'64': _ppc_stret
		},
		'arm': _arm_stret
	},
	objc_msgSendSuper2_stret: {
		'x86': _x86_stret,
		'ppc': {
			'32': _ppc_stret,
			'64': _ppc_stret
		},
		'arm': _arm_stret
	},
	/* fixup */
	objc_msgSend_fixup: {
		'x86': _x86,
		'ppc': {
			'32': _ppc,
			'64': _ppc
		},
		'arm': _arm
	},
	objc_msgSend_fpret_fixup: {
		'x86': _x86,
		'ppc': {
			'32': _ppc,
			'64': _ppc
		},
		'arm': _arm
	},
	objc_msgSendSuper2_fixup: {
		'x86': _x86,
		'ppc': {
			'32': _ppc,
			'64': _ppc
		},
		'arm': _arm
	},
	objc_msgSend_stret_fixup: {
		'x86': _x86_stret,
		'ppc': {
			'32': _ppc_stret,
			'64': _ppc_stret
		},
		'arm': _arm_stret
	},
	objc_msgSendSuper2_stret_fixup: {
		'x86': _x86_stret,
		'ppc': {
			'32': _ppc_stret,
			'64': _ppc_stret
		},
		'arm': _arm_stret
	}
};

const _object_c_block_methods = {
	objc_autoreleasePoolPush: function(instr, context, instructions) {
		if (!context.object_c.autorelease) {
			context.object_c.autorelease = [instr];
		} else {
			context.object_c.autorelease.push(instr);
		}
		instr.customflow = '@' + Global().printer.theme.flow('autoreleasepool');
		return Base.nop();
	},
	objc_autoreleasePoolPop: function(instr, context, instructions) {
		context.object_c.autorelease.pop().jump = instr.location;
		return Base.nop();
	},
	objc_retainAutoreleasedReturnValue: function() {
		return Base.nop();
	},
	objc_release: function() {
		var arch = Global().evars.arch;
		var bits = Global().evars.archbits.toString();
		return Base.objc_call(_object_c_registers[arch][bits][0], 'release');
	},
	objc_retain: function() {
		var arch = Global().evars.arch;
		var bits = Global().evars.archbits.toString();
		return Base.objc_call(_object_c_registers[arch][bits][0], 'retain');
	},
};

export default {
	arguments: function(callname) {
		callname = Extra.replace.call(callname);
		var arch = Global().evars.arch;
		var bits = Global().evars.archbits.toString();
		return _object_c_class_methods[callname][arch][bits].args.slice();
	},
	returns: function(callname) {
		callname = Extra.replace.call(callname);
		var arch = Global().evars.arch;
		var bits = Global().evars.archbits.toString();
		return _object_c_class_methods[callname][arch][bits].returns;
	},
	receiver: function(callname) {
		callname = Extra.replace.call(callname);
		var arch = Global().evars.arch;
		var bits = Global().evars.archbits.toString();
		return _object_c_class_methods[callname][arch][bits].receiver;
	},
	selector: function(callname) {
		callname = Extra.replace.call(callname);
		var arch = Global().evars.arch;
		var bits = Global().evars.archbits.toString();
		return _object_c_class_methods[callname][arch][bits].selector;
	},
	handle_others: function(callname, instr, context, instructions) {
		if (_object_c_block_methods[callname]) {
			if (!context.object_c) {
				context.object_c = {};
			}
			return _object_c_block_methods[callname](instr, context, instructions);
		}
		return null;
	},
	is_class_method: function(callname) {
		callname = Extra.replace.call(callname);
		return !!_object_c_class_methods[callname];
	},
	is: function(callname) {
		callname = Extra.replace.call(callname);
		return callname.startsWith('objc_') && (
			_object_c_block_methods[callname] ||
			_object_c_class_methods[callname]
		);
	}
};
